package com.gitee.linmt.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gitee.linmt.entity.Result;
import com.gitee.linmt.filter.authentication.JsonUsernamePasswordAuthenticationFilter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * SpringSecurity 配置类. EnableGlobalMethodSecurity(prePostEnabled = true) 开启方法注解的权限限制
 *
 * @author <a href="mailto:lin-mt@outlook.com">lin-mt</a>
 */
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    private final UserDetailsService userDetailsService;
    
    /**
     * 注入自定义的 UserDetailsServiceImpl.
     *
     * @param userDetailsService 使用我们自定义的 UserDetailsServiceImpl
     */
    public SpringSecurityConfig(@Qualifier("userDetailsService") final UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
    
    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        // 使用自定义的 UserDetailService 实现用户信息的加载
        auth.userDetailsService(userDetailsService);
    }
    
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // @formatter:off
        http.authorizeRequests()
                // 这是一种权限配置的方式，除了特殊的地址可能会在这里配置，大部分是采用注解的方式配置权限
                // .antMatchers("/roleAdmin").hasAnyRole("admin")
                .anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().csrf()
                // 关闭 csrf，什么是 csrf？
                // https://docs.spring.io/spring-security/site/docs/5.3.3.BUILD-SNAPSHOT/reference/html5/#servlet-csrf-using
                .disable().exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
                    // 自定义无权限访问时的返回信息
                    final Result<Object> result = Result.failure().setMessage("无权限访问");
                    responseJsonData(response, result);
                });
        final JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter = new JsonUsernamePasswordAuthenticationFilter();
        jsonUsernamePasswordAuthenticationFilter
                .setAuthenticationSuccessHandler((request, response, authentication) -> {
                    // 自定义登录成功后的返回值，authentication 就是我们验证成功后缓存的用户信息，此时已经把敏感数据自动去掉了，如果实现了接口 CredentialsContainer
                    final Result<Object> result = Result.success().setMessage("登陆成功").setData(authentication);
                    responseJsonData(response, result);
                });
        jsonUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler((request, response, exception) -> {
            // 自定义登录失败后的返回值
            final Result<Object> result = Result.failure().setMessage("登陆失败");
            responseJsonData(response, result);
        });
        // @formatter:on
        jsonUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
        http.addFilterAt(jsonUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
    
    private <T> void responseJsonData(final HttpServletResponse response, final Result<T> result) throws IOException {
        // 这里要设置 response 的 ContentType，否则返回中文会乱码
        // Spring 提供的 MediaType.APPLICATION_JSON_UTF8_VALUE 标注了过时，那就自己写个吧！
        response.setContentType("application/json;charset=UTF-8");
        final PrintWriter out = response.getWriter();
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();
        out.close();
    }
}